<?php

namespace App\Service;


use Hyperf\DbConnection\Db;
use Psr\Log\LoggerInterface;
use Hyperf\Logger\LoggerFactory;
use Hyperf\Coroutine\Coroutine;
use Hyperf\Context\ApplicationContext;
use Hyperf\Task\Annotation\Task;
use App\Task\TaskHandle;
/**
 * 充电桩通信协议解析与处理
 */
class MonitorService
{
    // 帧头
    const FRAME_HEADER = "5AA5";
    // 帧尾
    const FRAME_TAIL = "7887";
    public static $server;

    protected LoggerInterface $logger;


    public function __construct(LoggerFactory $loggerFactory)
    {
        // 第一个参数对应日志的 name, 第二个参数对应 config/autoload/logger.php 内的 key
        $this->logger = $loggerFactory->get('log', 'default');


    }


    private static function buildCommand($stationId, $cmd, $data,$zhen = '00',$err = '00')
    {
        $frameHeader = "5AA5";
        $frameTail = "7887";

        // 构建数据包
        $dataPacket = self::buildDataPacket($data);

        // 计算数据长度
        $dataLength = (strlen($dataPacket) / 2) + (strlen($err) / 2); // 数据长度为字节数
        $len = str_pad(dechex($dataLength), 2, '0', STR_PAD_LEFT);


        // 构建命令
        $command = $frameHeader . $stationId . $cmd .$zhen.$len .$err. $dataPacket;

        // 计算校验码
        $checksum = self::calculateChecksum($command);

        // 完整命令
        $fullCommand = $command . $checksum . $frameTail;

        $log = "回复设备指令---$stationId|$cmd|".$fullCommand;
        echo $log."\n";


        return strtoupper($fullCommand);
    }
    /**
     * 构建数据包
     * @param array $data 数据
     * @return string
     */
    private static function buildDataPacket($data)
    {
        $packet = '';
        foreach ($data as $key => $value) {
            $packet .= str_pad((string)$value, 2, '0', STR_PAD_LEFT);
        }
        return $packet;
    }

    private static function calculateChecksum($command)
    {
        return '0000';
    }

    /**
     * 解析接收到的数据
     * @param string $data 接收到的二进制数据
     * @param array $clientInfo 客户端信息（IP、端口等）
     * @param Swoole\Server $serv Swoole服务器实例
     */
    public static function parseData($data, $clientInfo, $serv)
    {
        self::$server = $serv;
        // 检查帧头和帧尾
        //5AA51100542908000100D3297887

        if (strlen($data) > 10 && ord($data[0]) == 53 && ord($data[1]) == 65 && ord($data[2]) == 65 && ord($data[3]) == 53) {
            $addrLength = self::getAddressLength($data);
            $macNo = self::getMacNo($data);
            // 保存连接信息到Redis
            $redisMac = [
                "macno" => $macNo,
                "ip" => $clientInfo['address'],
                "port" => $clientInfo['port'],
                "lastbeat" => time(),
                "lastbeat_date" => date('Y-m-d H:i:s',time()),
            ];
            self::setRedis($macNo  . ":COM", json_encode($redisMac));

            // 判断指令类型
            $save = [];
            $commandType = self::getCommand($data);
            switch ($commandType) {
//                case 0x01: // 登录指令
//                    return self::handleLogin($data, $clientInfo);
                case '08': // 心跳指令
                    self::createtask(\Hyperf\Support\env('CALLBACK_URL'),http_build_query($redisMac));//异步发POST，不阻塞等待接收
                    return self::handleHeartbeat($data, $clientInfo);
                    break;
//                case 0x03: // 数据上传指令
//                    return self::handleDataUpload($data);
                case 'A1'://签到回复
                    //5AA500000000 A1 00 01 01 00007887
                    return self::getPreCommand($data) . "A1000101" . self::getCrc16($data). self::FRAME_TAIL;
                    break;
                case '04'://充电关闭上报
                    //5AA51100542904000800 01 00 01 000000021CF57887
                    //5AA5110054290400080002000100000000AE347887 空载
                    $save['macno'] = $macNo;
                    $save['ctime'] = date('Y-m-d H:i:s',time());
                    $save['event'] = '04';
                    $save['desc'] = '充电关闭上报';
                    $save['socket_id'] = (int)substr($data, 20, 2);//插座号
                    $save['operate'] = (int)substr($data, 22, 2);//0关闭 1打开
                    $save['reason'] = (int)substr($data, 24, 2);//0：其它原因；1：空载；2：充满（功率<25W）；3：过载（实际功率大于本机最大允许功率）。 4：本机主电源线温度过高
                
                    self::createtask(\Hyperf\Support\env('RECORD_URL'),http_build_query($save));
                    return self::getPreCommand($data) . "04000101" . self::getCrc16($data). self::FRAME_TAIL;
                    break;
                case '02'://刷卡查询余额
                    //5AA511005429 02 00 09 01 0000000001034033 E1C07887
                    $cardNumber = ltrim(substr($data, 20, 16),'0');
                    $card = Db::table('xc_iccard')->where('number','=',$cardNumber)->first();
                    $err_code = substr($data, 18, 2);
                    if($card){
                        if($card['status']!=1 || empty($card['uid'])){
                            return self::getPreCommand($data) . "0200050000000000".$err_code . self::getCrc16($data). self::FRAME_TAIL;
                        }
                        $money = Db::table('xc_money')->where('user_id','=',$card['uid'])->value('money');
                        $money = $money?(float)$money:0;
                        $money_hex = str_pad(strtoupper(dechex((int)bcmul($money,100,0))), 8, '0', STR_PAD_LEFT);
                        //5AA51100542902000501000186A00100007887 发送
                        return self::getPreCommand($data) . "02000501".$money_hex.$err_code . self::getCrc16($data). self::FRAME_TAIL;
                    }else{
                        return self::getPreCommand($data) . "0200050500000000".$err_code . self::getCrc16($data). self::FRAME_TAIL;
                    }
                    break;
                case '03'://刷打开插座上报
                    //5AA51100542903000B00 02 01 0000000001034033 39977887 开始充电
                    $save['macno'] = $macNo;
                    $save['ctime'] = date('Y-m-d H:i:s',time());
                    $save['event'] = '03';
                    $save['desc'] = '刷打开插座上报';
                    $save['socket_id'] = (int)substr($data, 20, 2);//插座号
                    $save['operate'] = (int)substr($data, 22, 2);//0关闭 1打开
                    $save['cardnum'] = ltrim(substr($data, 24, 16),'0');
                    self::createtask(\Hyperf\Support\env('RECORD_URL'),http_build_query($save));
                    return self::getPreCommand($data) . "03000101" . self::getCrc16($data). self::FRAME_TAIL;
                    break;

                case '54':
                    $getData = redis()->get("{$macNo}:SET");
                    if(!$getData){
                        $msg=date("回复设置从机{$macNo}---Y-m-d H:i:s")."----无数据\n";
                        echo $msg;
                        break;
                    }
                    $getData = json_decode($getData,true);
                    $sockets = json_decode($getData['data'],true);
                    $pack = [];
                    foreach ($sockets as $s){
                        $pack[] = $s['sn'].str_pad((string)$s['number'], 2, '0', STR_PAD_LEFT);
                    }
                    return self::buildCommand($macNo,'54', $pack,'01','01');
                    break;
                default:
                    // 其他指令处理
                    break;
            }
            if($save) redis()->set($macNo.":".$commandType,json_encode(array("ctime"=>time(),"data"=>$save)));

//            //机器主动指令
//            if(ord($data[$addrLength+5])==56){
//
//            } else{//机器应答指令
//                $addrLength = self::getAddressLength($data);
//                $macno = getmacno($data);
//                $save['macno'] = $macno;
//                $content = '机器指令应答';
//                $com = strtoupper(bin2hex(substr($data,$addrLength+4,2)));
//                $start = $addrLength+10;
//                switch($com){
//                    case '0A04': //单纯卡上金额提示
//                    case '0A09': //错误提示
//                        $save['result'] = hexdec(bin2hex(substr($data,$start,1)));
//                        $start += 1;
//                        break;
//                    case '0A05': // 打开充电通道并带金额提示
//                    case '0A07': // 普通打开充电通道
//                    case '0A08': // 关闭充电通道
//                    case '0A0A': // 打开交流充电通道加强版
//                        $save['pipes'] = hexdec(bin2hex(substr($data,$start,1))); //通道号
//                        $start += 1;
//                        $save['result'] = hexdec(bin2hex(substr($data,$start,1)));
//                        $start += 1;
//                        $save['time'] = time();
//                        break;
//                    case "0A0E": //查询单路充电状态
//                        $save['pipes'] = hexdec(bin2hex(substr($data,$start,1))); //通道号
//                        $start += 1;
//                        $save['status'] = hexdec(bin2hex(substr($data,$start,1))); //充电状态: 0 关，1 开，2 保险烧，3 继电器损坏，4 继电器损坏，255 未指定
//                        $start += 1;
//                        $save['mode'] = hexdec(bin2hex(substr($data,$start,1))); //充电模式:0 计时，1 电量，255 未指定
//                        $start += 1;
//                        $save['currentelectric'] = hexdec(bin2hex(substr($data,$start,4))); //实时电流:单位为毫安
//                        $start += 4;
//                        $save['value'] = hexdec(bin2hex(substr($data,$start,4))); //已用电量/剩余时间 计时模式单位为秒，电量模式单位为瓦时（千分之一度）
//                        $start += 4;
//                        break;
//                    case "0A0F"://查询全部路充电状态
//                        switch (ord($data[$addrLength+14])){
//                            case 0x06:
//                                $macdata = substr($data,$addrLength+17,hexdec(bin2hex(substr($data,$addrLength+15,2))));
//                                $start = 0;
//                                $save['history_quantity']=hexdec(bin2hex(substr($macdata,$start,2)))/10;//历史电量
//                                $start+=2;
//                                $save['pipes'] = hexdec(bin2hex(substr($data,$start,1))); //通道数
//                                $start += 1;
//                                $save['fire'] = hexdec(bin2hex(substr($data,$start,1))); // 火警
//                                $start += 1;
//                                $status = str_split(str_pad(base_convert(bin2hex(substr($data,$start,2)),16,2),16,'0',STR_PAD_LEFT),1);
//                                $start += 2;
//                                for ($i = 0; $i < $save['pipes']; $i++){
//                                    $save['status'][$i] = ['is_open'=>$status[$i],'current'=>hexdec(bin2hex(substr($data,$start,2)))];
//                                    $start += 2;
//                                }
//                                break;
//                            case 0x40:
//                                $macdata = substr($data,$addrLength+17,hexdec(bin2hex(substr($data,$addrLength+15,2))));
//                                $start = 0;
//                                $save['history_quantity']=hexdec(bin2hex(substr($macdata,$start,4)));//历史电量
//                                $start+=4;
//                                $save['smokefeel'] = implode(',',str_split(str_pad(base_convert(bin2hex(substr($data,$start,1)),16,2),8,'0',STR_PAD_LEFT),1)); //烟感
//                                $start += 2;
//                                $save['vol'] = hexdec(bin2hex(substr($data,$start,1))); //电压: 0-255 V
//                                $start += 1;
//                                //插入状态: 1 插入，0 未插入
//                                $save['charge_status'] = implode(',',array_reverse(str_split(str_pad(base_convert(bin2hex(substr($data,$start,2)),16,2),16,'0',STR_PAD_LEFT),1)));
//                                $start += 2;
//                                //GPRS信号强度: 值为0--32
//                                $save['gprs'] = hexdec(bin2hex(substr($data,$start,1)));$start += 2;
//                                // 通道数
//                                $save['pipes'] = hexdec(bin2hex(substr($data,$start,1)));$start += 1;
//                                for($i=0;$i<$save['pipes'];$i++){
//                                    $dataarr['is_open'] = hexdec(bin2hex(substr($macdata,$start,1)));$start+=1;
//                                    $dataarr['mode'] =hexdec(bin2hex(substr($macdata,$start,1)));$start+=1;
//                                    $dataarr['currentelectric'] =hexdec(bin2hex(substr($macdata,$start,2)));$start+=2;
//                                    $dataarr['value'] =hexdec(bin2hex(substr($macdata,$start,4)));$start+=4;
//                                    $save['pipestatus'][] = $dataarr;
//                                }
//                                break;
//                            case 0x4A:
//                                $macdata = substr($data,$addrLength+17,hexdec(bin2hex(substr($data,$addrLength+15,2))));
//                                $start = 0;
//                                $save['history_quantity']=hexdec(bin2hex(substr($macdata,$start,4)));//历史电量
//                                $start+=4;
//                                $val=bin2hex(substr($macdata,$start,1));//烟感
//                                $start+=1;
//                                $str = "";
//                                $smokfeel=str_pad(base_convert($val,16,2),8,"0",STR_PAD_LEFT);//烟感
//                                for($i=0;$i<8;$i++){
//                                    $str .= substr($smokfeel,$i,1).",";
//                                }
//                                $save['smokefeel'] = trim($str,",");
//
//                                $val=hexdec(bin2hex(substr($macdata,$start,1)));//温度
//                                $start+=1;
//
//                                if($val>127){
//                                    $val = 127-$val;
//                                }
//                                $save['temperature'] = $val;
//
//                                $val=hexdec(bin2hex(substr($macdata,$start,2)));//电压
//                                $start+=2;
//                                $save['vol'] = $val;
//                                $save['charge_status']=implode(",",str_split(str_pad(base_convert(bin2hex(substr($macdata,$start,1)),16,2),8,"0",STR_PAD_LEFT),1));//充电枪是否已插入
//                                $start+=1;
//                                $save['rssi']=hexdec(bin2hex(substr($macdata,$start,1)));//信号强度
//                                $start+=1;
//                                $start+=1;
//                                $val=hexdec(bin2hex(substr($macdata,$start,1)));//插座数
//                                $start+=1;
//                                $save['pipes'] = $val;
//                                for($i=0;$i<$save['pipes'];$i++){
//                                    $dataarr['is_open'] = hexdec(bin2hex(substr($macdata,$start,1)));$start+=1;
//                                    $dataarr['mode'] =hexdec(bin2hex(substr($macdata,$start,1)));$start+=1;
//                                    $dataarr['currentelectric'] =hexdec(bin2hex(substr($macdata,$start,4)));$start+=4;
//                                    $dataarr['value'] =hexdec(bin2hex(substr($macdata,$start,4)));$start+=4;
//                                    $save['pipestatus'][] = $dataarr;
//                                }
//
//                                break;
//                        }
//                        break;
//                    case '0150'://充电桩充满自停和拔出自停参数读取
//                    case '01D0'://充电桩充满自停和拔出自停参数设置
//                        //充满自停门槛电流值
//                        $save['charge_full_curr'] = hexdec(bin2hex(substr($data,$start,2)));$start+=2;
//                        //充满自停判断时间
//                        $save['charge_full_time'] = hexdec(bin2hex(substr($data,$start,2)));$start+=2;
//                        //拔出自停门槛电流值
//                        $save['out_curr'] = hexdec(bin2hex(substr($data,$start,2)));$start+=2;
//                        //拔出自停判断时间
//                        $save['out_time'] = hexdec(bin2hex(substr($data,$start,2)));$start+=2;
//                        break;
//                    // 升级
//                    case "0F01";
//                        $save['mac_type'] = bin2hex(substr($data,$addrLength+10,4));
//                        $save['version'] = bin2hex(substr($data,$addrLength+14,4));
//                        break;
//                    case "0F02";
//                        $save['filelength'] = hexdec(bin2hex(substr($data,$addrLength+10,4)));
//                        $save['filesum'] = hexdec(bin2hex(substr($data,$addrLength+14,4)));
//                        $save['datalen'] = hexdec(bin2hex(substr($data,$addrLength+18,4)));
//                        $save['datalength'] = hexdec(bin2hex(substr($data,$addrLength+22,2)));
//                        $save['datasum'] = hexdec(bin2hex(substr($data,$addrLength+24,4)));
//                        $save['status'] = hexdec(bin2hex(substr($data,$addrLength+28,1)));
//                        break;
//                    case "0F03";
//                        $save['filelength'] = hexdec(bin2hex(substr($data,$addrLength+10,4)));
//                        $save['filesum'] = hexdec(bin2hex(substr($data,$addrLength+14,4)));
//                        $save['datalen'] = hexdec(bin2hex(substr($data,$addrLength+18,4)));
//                        $save['datalength'] = hexdec(bin2hex(substr($data,$addrLength+22,2)));
//                        $save['datasum'] = hexdec(bin2hex(substr($data,$addrLength+24,4)));
//                        $save['status'] = hexdec(bin2hex(substr($data,$addrLength+28,1)));
//                        break;
//
//                }
//                redis()->set($macno.":".$com,json_encode(array("ctime"=>time(),"data"=>$save)));
//                if(!empty($command)) return $command.getcrc16($command);
//            }
        }
    }



    /**
     * 处理登录指令
     * @param string $data 接收到的二进制数据
     * @param array $clientInfo 客户端信息
     * @return string 返回给客户端的响应
     */
    private static function handleLogin($data, $clientInfo)
    {
        $command = self::getPreCommand($data) . "0003090100";
        return $command . self::getCrc16($command);
    }

    /**
     * 处理心跳指令
     * @param string $data 接收到的二进制数据
     * @param array $clientInfo 客户端信息
     * @return string 返回给客户端的响应
     */
    private static function handleHeartbeat($data, $clientInfo)
    {
        $addrLength = self::getAddressLength($data);
        return self::getPreCommand($data) . "08000100" . self::getCrc16($data). self::FRAME_TAIL;
    }

    /**
     * 处理数据上传指令
     * @param string $data 接收到的二进制数据
     * @return string 返回给客户端的响应
     */
    private static function handleDataUpload($data)
    {
        $command = self::getPreCommand($data) . "00050903" . self::getChildChar($data, 0, 2) . "00";
        self::saveRecord($data);
        return $command . self::getCrc16($command);
    }

    /**
     * 保存记录到Redis
     * @param string $data 接收到的二进制数据
     */
    private static function saveRecord($data)
    {
        $macNo = self::getMacNo($data);
        $start = 19;
        $nums = hexdec(bin2hex(substr($data, $start, 1)));
        $start += 2;

        if ($nums) {
            for ($i = 0; $i < $nums; $i++) {
                $dumpData = [];
                $start += 1;
                $dumpData['event'] = strtoupper(bin2hex(substr($data, $start, 1))); // 事件
                $start += 1;
                $dumpData['ctime'] = hexdec(bin2hex(substr($data, $start, 2))) . "-"; // 年
                $start += 2;
                $dumpData['ctime'] .= substr("0" . hexdec(bin2hex(substr($data, $start, 1))), -2) . "-"; // 月
                $start += 1;
                $dumpData['ctime'] .= substr("0" . hexdec(bin2hex(substr($data, $start, 1))), -2) . " "; // 日
                $start += 1;
                $dumpData['ctime'] .= substr("0" . hexdec(bin2hex(substr($data, $start, 1))), -2) . ":"; // 时
                $start += 1;
                $dumpData['ctime'] .= substr("0" . hexdec(bin2hex(substr($data, $start, 1))), -2) . ":"; // 分
                $start += 1;
                $dumpData['ctime'] .= substr("0" . hexdec(bin2hex(substr($data, $start, 1))), -2); // 秒
                $start += 1;
                $dumpData['pipes'] = hexdec(bin2hex(substr($data, $start, 2))); // 通道号
                $start += 2;
                $dumpData['money'] = hexdec(bin2hex(substr($data, $start, 4))); // 交易金额
                $start += 4;
                $dumpData['cardnum'] = (strtoupper(bin2hex(substr($data, $start, 4))) != "FFFFFFFF") ? hexdec(self::reverseData(bin2hex(substr($data, $start, 4)))) : ""; // 卡号
                $start += 4;
                $dumpData['electrics'] = hexdec(bin2hex(substr($data, $start, 4))); // 用电量
                $start += 5;
                $dumpData['macno'] = $macNo;

                // 保存到Redis
                self::setRedis($macNo . ":record", json_encode($dumpData));

                // 异步发送数据到服务器
                $recordUrl = "http://wx.szhuilaidian.com/vv/iothub/api/index/cardUpload";
                self::createTask($recordUrl, http_build_query($dumpData));
            }
        }
    }

    /**
     * 获取地址长度
     * @param string $data 二进制数据
     * @return int 地址长度
     */
    private static function getAddressLength($data)
    {
        // 根据协议获取地址长度
        return 8; // 假设地址长度为4字节
    }

    /**
     * 获取设备编号
     * @param string $data 二进制数据
     * @return string 设备编号
     */
    private static function getMacNo($data)
    {
        // 根据协议获取设备编号
        return substr($data, 4, 8); // 假设设备编号为4字节
    }

    /**
     * 获取设备编号
     * @param string $data 二进制数据
     * @return string 设备编号
     */
    private static function getCommand($data)
    {
        // 根据协议获取设备编号
        return substr($data, 12, 2); // 假设设备编号为4字节
    }

    /**
     * 获取前置命令
     * @param string $data 二进制数据
     * @return string 前置命令
     */
    private static function getPreCommand($data)
    {
        // 根据协议生成前置命令
        return substr($data, 0, 12); // 假设前置命令为8字节
    }

    /**
     * 获取后置命令
     * @param string $data 二进制数据
     * @return string 前置命令
     */
    private static function getLastCommand($data)
    {
        // 根据协议生成前置命令
        return substr($data, -4); // 假设前置命令为8字节
    }

    /**
     * 获取CRC16校验码
     * @param string $data 二进制数据
     * @return string CRC16校验码
     */
    private static function getCrc16($data)
    {
        // 根据协议计算CRC16校验码
        //return getcrc16($data); // 假设CRC16校验码为4字节
        return '0000';
    }

    /**
     * 获取时间戳
     * @param int $time 时间戳
     * @return string 格式化后的时间
     */
    private static function getTime($time)
    {
        // 根据协议格式化时间
        return date("YmdHis", $time);
    }

    /**
     * 反转数据
     * @param string $data 二进制数据
     * @return string 反转后的数据
     */
    private static function reverseData($data)
    {
        // 反转数据
        return strrev($data);
    }

    /**
     * 设置Redis数据
     * @param string $key Redis键
     * @param string $value Redis值
     */
    private static function setRedis($key, $value)
    {
        // 保存数据到Redis
        // 这里假设你已经有一个Redis连接
        // $redis->set($key, $value);
        redis()->set($key,$value);
    }

//    /**
//     * 创建异步任务
//     * @param string $url 请求URL
//     * @param string $data 请求数据
//     */
//    private static function createTask($url, $data)
//    {
//        // 创建异步任务
//        // 这里假设你有一个任务队列系统
//        // $taskQueue->addTask($url, $data);
//    }


    /**
     * 创建异步任务
     */
    static function createtask($url,$data,$type=1,$anotherdata=array()){
        $taskdata['data'] = $data;
        $taskdata['url'] = $url;
        $taskdata['type'] = $type;
        if(count($anotherdata)>0){
            foreach($anotherdata as $k=>$v){
                $taskdata[$k] = $v;
            }
        }
        $container = ApplicationContext::getContainer();
        $task = $container->get(TaskHandle::class);
        $result = $task->handle(Coroutine::id(),$taskdata);

        return 1;
    }
}